package com.github.chirspan.xaas.soa.message.xml;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.github.chirspan.xaas.soa.message.core.SOAConstants;
import com.github.chirspan.xaas.soa.message.factory.ValueHandlerFactory;
import com.github.chirspan.xaas.soa.message.handler.IValueHandler;
import com.github.chirspan.xaas.soa.message.util.XmlXPathUtil;
import com.github.chirspan.xaas.soa.message.xml.namespace.MapNamespaceContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.*;

/**
 * @className: XMLMessageUtils
 * @description: <p></p>
 * @author: chenpan
 * @date: 2021/9/27 17:35
 **/
public class XMLMessageUtils {

    private final XmlXPathUtil xpathUtil = new XmlXPathUtil();

    protected boolean hasChild(Map<String, Object> rule) {
        return rule.get(SOAConstants.SCHEMA_KEY_CHILD) != null;
    }

    protected boolean isSingleNode(Map<String, Object> rule) {
        //return BooleanUtil.isFalse((Boolean) rule.get(SOAConstants.SCHEMA_KEY_MULTIPLICITY));
        String multiplicity = (String) rule.get(SOAConstants.SCHEMA_KEY_MULTIPLICITY);
        return !StrUtil.isNotEmpty(multiplicity)
                || multiplicity.indexOf(SOAConstants.SCHEMA_MULTIPLICITY_MULTI_CHAR) < 0;
    }

    private boolean isNeedCopy(int nodeCount, int contentCount) {
        return (nodeCount == 1 && contentCount >= nodeCount);
    }

    /**
     * 组织XML，填充节点
     *
     * @param node
     * @param schema
     * @param nsc
     * @param content
     * @return
     * @throws Exception
     */
    public boolean fillNode(Node node, Object schema,
                            MapNamespaceContext nsc, Object content)
            throws Exception {
        if ((node != null) && ((schema instanceof Map)) && (nsc != null) && ((content instanceof Map))) {
            Object value;
            for (Map.Entry<String, Object> entity : ((Map<String, Object>) schema).entrySet()) {
                Map<String, Object> rule = (Map) entity.getValue();
                value = ((Map) content).get(rule.get(SOAConstants.SCHEMA_KEY_PROPERTY));
                if ((value == null) || ("".equals(value))) {
                    String controlNode = (String) rule.get(SOAConstants.SCHEMA_KEY_CONTROL_NODE);
                    NodeList nodeList = this.xpathUtil.getListValue(controlNode, node, nsc);
                    if (nodeList != null) {
                        Node parentNode = nodeList.item(0).getParentNode();
                        for (int i = 0; i < nodeList.getLength(); i++) {
                            Node oldChild = nodeList.item(i);
                            try {
                                parentNode.removeChild(oldChild);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                } else {
                    processWritingSchema(rule, node, nsc, value);
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 解析XML，获取节点数据
     *
     * @param node
     * @param schema
     * @param nsc
     * @return
     */
    public Map<String, Object> getNodeContent(Node node, Object schema,
                                              MapNamespaceContext nsc) {
        if (node != null && schema instanceof Map && nsc != null) {
            Map<String, Object> mapResult = new HashMap<String, Object>();
            String key;
            Object value;

            for (Map.Entry<String, Object> entity : ((Map<String, Object>) schema).entrySet()) {
                key = entity.getKey();
                Map<String, Object> rule = (Map<String, Object>) entity.getValue();
                value = processReadingSchema(rule, node, nsc);
                mapResult.put(key, value);
            }
            return mapResult;
        }
        return null;
    }

    private void processWritingSchema(Map<String, Object> rule, Node node,
                                      MapNamespaceContext nsc, Object content)
            throws Exception {
        if (node != null && rule != null && nsc != null
                && content != null) {
            String xpathStr = (String) rule.get(SOAConstants.SCHEMA_KEY_PATH);
            NodeList nodeList = xpathUtil.getListValue(xpathStr, node, nsc);

            if ((nodeList == null) || (nodeList.getLength() <= 0)) {
                return;
            }
            Map<String, Map<String, Object>> child = (Map<String, Map<String, Object>>) rule.get(SOAConstants.SCHEMA_KEY_CHILD);
            boolean hasChild = hasChild(rule);

            if (isSingleNode(rule)) {
                // single node
                Object value = null;
                if (content instanceof Collection) {
                    Object[] contentList = ((Collection<?>) content).toArray();
                    if (contentList.length > 0) {
                        value = contentList[0];
                    }
                } else {
                    value = content;
                }

                if (nodeList.getLength() == 1) {
                    Node tmpNode = (Node) nodeList.item(0);
                    if (value != null) {
                        if (hasChild && (content instanceof Collection || content instanceof Map)) {
                            fillNode(tmpNode, child, nsc, value);
                            return;
                        }
                        setNodeValue(tmpNode, value, rule);
                    } else {
                    }
                } else {
                    throw new Exception("path:[" + xpathStr
                            + "]. Can not get content from message:[" + content
                            + "]");
                }
            } else {
                // multiple node
                if (content instanceof Collection) {
                    Object[] contentList = ((Collection<?>) content).toArray();
                    int nodeCount = nodeList.getLength();
                    int contentCount = contentList.length;
                    boolean cloneFlag = isNeedCopy(nodeCount, contentCount);

                    if ((nodeCount == contentCount && nodeCount > 0)
                            || cloneFlag) {
                        Node cloneNode;
                        Node templateNode = nodeList.item(0);
                        Node parentNode = templateNode.getParentNode();

                        for (int i = 0; i < contentCount; i++) {
                            if (cloneFlag && i > 0) {
                                cloneNode = templateNode.cloneNode(true);
                                parentNode.appendChild(cloneNode);
                            } else {
                                cloneNode = nodeList.item(i);
                            }

                            if (hasChild) {
                                fillNode(cloneNode, child, nsc, contentList[i]);
                            } else {
                                setNodeValue(cloneNode, contentList[i], rule);
                            }
                        }
                    } else if (contentCount == 0) {
                        // TODO
                    } else {
                        throw new Exception("path:[" + xpathStr
                                + "]. Can not get content from message:["
                                + content + "]");
                    }
                } else {
                    throw new Exception("path:[" + xpathStr
                            + "]. Can not get content from message:[" + content
                            + "]");
                }
            }
        }
    }

    private Object processReadingSchema(Map<String, Object> rule, Node node,
                                        MapNamespaceContext nsc) {
        if (node != null && rule instanceof Map && nsc != null) {
            String xpathStr = (String) rule.get(SOAConstants.SCHEMA_KEY_PATH);
            NodeList nodeList = xpathUtil.getListValue(xpathStr, node, nsc);

            Map<String, Map<String, Object>> child = (Map<String, Map<String, Object>>) rule.get(SOAConstants.SCHEMA_KEY_CHILD);
            boolean hasChild = hasChild(rule);
            if (isSingleNode(rule)) {
                if (nodeList.getLength() > 0) {
                    Node tmpNode = (Node) nodeList.item(0);
                    if (hasChild) {
                        return getNodeContent(tmpNode, child, nsc);
                    }
                    return getNodeValue(tmpNode, rule);
                } else {
                    return null;
                }
            } else {
                List<Object> listResult = new ArrayList<>();
                for (int i = 0; i < nodeList.getLength(); i++) {
                    Node tmpNode = nodeList.item(i);
                    if (hasChild) {
                        listResult.add(getNodeContent(tmpNode, child, nsc));
                    } else {
                        listResult.add(getNodeValue(tmpNode, rule));
                    }
                }
                return listResult;
            }
        }
        return null;
    }

    private void setNodeValue(Node node, Object value,
                              Map<String, Object> rule) {
        String format = (String) rule.get(SOAConstants.SCHEMA_KEY_FORMAT);
        String type = (String) rule.get(SOAConstants.SCHEMA_KEY_TYPE);
        IValueHandler valueHandler = ValueHandlerFactory.getValueHandler(type);
        String expressionStr = (String) rule.get(SOAConstants.SCHEMA_KEY_EXPRESSION);
        if (StrUtil.isNotEmpty(expressionStr)) {
            ExpressionParser parser = new SpelExpressionParser();
            value = parser.parseExpression(expressionStr);
        }
        String retValue = valueHandler.getWritingValue(value, format);
        if (StrUtil.isBlank(retValue)) {
            retValue = (String) rule.get(SOAConstants.SCHEMA_KEY_DEFAULT_VALUE);
        }
        node.setTextContent(retValue);
    }

    private Object getNodeValue(Node node, Map<String, Object> rule) {
        String format = (String) rule.get(SOAConstants.SCHEMA_KEY_FORMAT);
        String type = (String) rule.get(SOAConstants.SCHEMA_KEY_TYPE);
        IValueHandler valueHandler = ValueHandlerFactory.getValueHandler(type);
        Object value = node.getTextContent();

        value = valueHandler.getReadingValue(value, type, format);

        return value != null ? value : (String) rule.get(SOAConstants.SCHEMA_KEY_DEFAULT_VALUE);
    }
}
